package arkham.data;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.util.*;

import arkham.data.Globals.CommonItemSubtype;
import arkham.data.Globals.EffectUsage;
import arkham.data.Globals.InvestigatorCardType;
import arkham.data.Globals.InvestigatorSkill;
import arkham.data.Globals.MoveState;
import arkham.data.Globals.UniqueItemSubtype;
import arkham.data.Globals.UsablePhase;
import arkham.jaxb.encounter.*;
import arkham.util.AHLogger;
import arkham.util.ImageUtils;
import arkham.util.Pair;

/**
 * This class represents an investigator. It consists of the following
 * data:
 * <ul>
 * <li>Basic investigator stats that are shown on each character sheet:
 * Maximum stat values, skill values, class powers
 * <li>Starting random and fixed possessions
 * <li>Back story
 * <li>Tracking of current investigator statistics, including:
 * Current clues, money, possessions, stats, skills, and status effects
 * <li>Current investigator location
 * </ul>
 * @author ctheng
 *
 */
public class Investigator implements NamedEntity
{

	/* Info obtained from XML file */
	private String name;
	private String profession;
	private int maxStamina, maxSanity;
	private int[] baseSkill;
	private int baseFocus, focus;
	private String startLocation;
	private String backgroundStory;
	private List<InvestigatorPower> specialPowers;
	private BufferedImage img, rotatedImg, activePanelImage, inactivePanelImage;
	private Color textColor;

	/* Info about starting possessions */
	private int startMoney;
	private int startClues;
	private List<Pair<String, String>> fixedPossessions;
	private List<Pair<String, Integer>> randomPossessions;

	/* In game information */
	private String player;
	private int index;
	private int clues, money, focusMod;
    private int movementPoints;
	private int[] skillSlider;
	private int[] skillMod;
	private boolean skillSlidersSet, dead;
	private int curStamina, curSanity, maxStaminaMod, maxSanityMod;
	private String location;
	private boolean delayed, movementEffectsLocked, movementPointsLocked;
	private boolean avoidedMonsters, exploredMarker;
	private LocationContainer locContainer;
	private List<PersonalEffect> statusEffects;
	private List<InvestigatorCard> invcards;
	private List<Monster> monsterTrophies;
	private List<GateTile> gateTrophies;
	private UsableEffect currentUsedEffect;

	public Investigator(String name)
	{
		this.name = name;
		specialPowers = new ArrayList<InvestigatorPower>();
		fixedPossessions = new ArrayList<Pair<String, String>>();
		randomPossessions = new ArrayList<Pair<String, Integer>>();
		statusEffects = new ArrayList<PersonalEffect>();
		invcards = new ArrayList<InvestigatorCard>();
		monsterTrophies = new ArrayList<Monster>();
		gateTrophies = new ArrayList<GateTile>();
		baseSkill = new int[6];
		skillSlider = new int[3];
		skillMod = new int[6];
	}

	/**
	 * Initializes this character for play. This method is called before
	 * the game begins and after characters have been selected by the
	 * players.
	 * <br>
	 * Things to do:
	 * <ul>
	 * <li>Initialize/finalize starting possessions
	 * <li>Place character at start location
	 * <li>Allow investigator to decide starting skills
	 * </ul>
	 */
	public void initialize()
	{
		curStamina = maxStamina - 1;
		curSanity = maxSanity - 1;
		focus = baseFocus;
		clues = startClues;
		money = startMoney;
		delayed = false;
		skillSlidersSet = false;
		focusMod = 0;
		Arrays.fill(skillSlider, 0);
		Arrays.fill(skillMod, 0);
	}

	/**
	 * Gets the investigator's name
	 * @return the name
	 */
	public String getName() {
		return name;
	}

	/**
	 * Gets the investigator's profession
	 * @return the profession
	 */
	public String getProfession() {
		return profession;
	}

	/**
	 * Gets the maximum stamina
	 * @return the maximum stamina
	 */
	public int getMaxStamina() {
		return maxStamina;
	}

	/**
	 * Gets the maximum stamina with modifiers applied
	 * @return the modified max stamina
	 */
	public int getMaxStaminaMod()
	{
		return maxStaminaMod + maxStamina;
	}

	/**
	 * Gets the maximum sanity
	 * @return the maximum sanity
	 */
	public int getMaxSanity() {
		return maxSanity;
	}

	/**
	 * Gets the maximum sanity with modifiers applied
	 * @return the modified max sanity
	 */
	public int getMaxSanityMod()
	{
		return maxSanityMod + maxSanity;
	}

	/**
	 * Gets the base value for a skill
	 * @param index the index of the skill
	 * @return the base value of that skill
	 */
	public int getBaseSkill(int index) {
		if (index < 0 || index > 5) return -1;
		return baseSkill[index];
	}

	/**
	 * Gets the base sneak
	 * @return the investigator's base sneak
	 */
	public int getBaseSneak()
	{
		return getBaseSkill(InvestigatorSkill.SNEAK.index());
	}

	/**
	 * Gets the base speed
	 * @return the investigator's base speed
	 */
	public int getBaseSpeed()
	{
		return getBaseSkill(InvestigatorSkill.SPEED.index());
	}

	/**
	 * Gets the base fight
	 * @return the investigator's base fight
	 */
	public int getBaseFight()
	{
		return getBaseSkill(InvestigatorSkill.FIGHT.index());
	}

	/**
	 * Gets the base will
	 * @return the investigator's base will
	 */
	public int getBaseWill()
	{
		return getBaseSkill(InvestigatorSkill.WILL.index());
	}

	/**
	 * Gets the base lore
	 * @return the investigator's base lore
	 */
	public int getBaseLore()
	{
		return getBaseSkill(InvestigatorSkill.LORE.index());
	}

	/**
	 * Gets the base luck
	 * @return the investigator's base luck
	 */
	public int getBaseLuck()
	{
		return getBaseSkill(InvestigatorSkill.LUCK.index());
	}

	/**
	 * Gets the current skill value
	 * @param index the skill index
	 * @return the current skill value
	 */
	public int getCurrentSkill(int index)
	{
		// TODO: exception for Trish
		if (index < 0 || index > 5) return -1;
		boolean blue = (index % 2 == 0);
		int sliderno = index / 2;
		if (blue)
		{
			return baseSkill[index] + skillMod[index] + skillSlider[sliderno];
		}
		else
		{
			return baseSkill[index] + skillMod[index] - skillSlider[sliderno];
		}
	}

	/**
	 * Gets the investigator's current speed
	 * @return the current speed
	 */
	public int getCurrentSpeed()
	{
		return getCurrentSkill(InvestigatorSkill.SPEED.index());
	}

	/**
	 * Gets the investigator's current sneak
	 * @return the current sneak
	 */
	public int getCurrentSneak()
	{
		return getCurrentSkill(InvestigatorSkill.SNEAK.index());
	}

	/**
	 * Gets the investigator's current fight
	 * @return the current fight
	 */
	public int getCurrentFight()
	{
		return getCurrentSkill(InvestigatorSkill.FIGHT.index());
	}

	/**
	 * Gets the investigator's current will
	 * @return the current will
	 */
	public int getCurrentWill()
	{
		return getCurrentSkill(InvestigatorSkill.WILL.index());
	}

	/**
	 * Gets the investigator's current lore
	 * @return the current lore
	 */
	public int getCurrentLore()
	{
		return getCurrentSkill(InvestigatorSkill.LORE.index());
	}

	/**
	 * Gets the investigator's current luck
	 * @return the current luck
	 */
	public int getCurrentLuck()
	{
		return getCurrentSkill(InvestigatorSkill.LUCK.index());
	}

	/**
	 * Gets the investigator's base focus
	 * @return the base focus
	 */
	public int getBaseFocus()
	{
		return baseFocus;
	}

	/**
	 * Returns the investigator's current focus
	 * @return the current focus
	 */
	public int getCurrentFocus()
	{
		return Math.max(0, baseFocus + focusMod);
	}

	/**
	 * Checks the investigator's items to see if there are
	 * any changes to the skill modifiers
	 */
	public void calculateSkillMods()
	{
		Arrays.fill(skillMod, 0);
		List<ModEffect> modlist = searchModifiers("Skill Bonus");
		for (ModEffect mod : modlist)
		{
			if (mod.getUsage().equals(EffectUsage.ALWAYS))
			{
				String skillname = mod.getDetails();
				InvestigatorSkill skill = InvestigatorSkill.getInvestigatorSkill(skillname);
				if (skill != null)
				{
					skillMod[skill.index()] += mod.getAmountAsInt();
				}
				else if (skillname.equals("All"))
				{
				    for (int i = 0; i < skillMod.length; i++)
				    {
				        skillMod[i] += mod.getAmountAsInt();
				    }
				}
			}
		}
	}

	/**
	 * Gets the modifier amount for a skill
	 * @param index the index of the skill
	 * @return the amount the skill is modified by from its base value
	 */
	public int getSkillMod(int index)
	{
		if (index < 0 || index > 5) return -9999;
		return skillMod[index];
	}

	/**
	 * Gets the investigator's current focus
	 * @return the current focus
	 */
	public int getFocus() {
		return focus;
	}

	/**
	 * Sets the investigator's current focus
	 * @param focus the current focus
	 */
	public void setFocus(int focus)
	{
		this.focus = focus;
	}

	/**
	 * Gets the name of the start location
	 * @return the name of the start location
	 */
	public String getStartLocation() {
		return startLocation;
	}

	/**
	 * Gets the background story of this investigator
	 * @return the background story
	 */
	public String getBackgroundStory() {
		return backgroundStory;
	}

	/**
	 * Gets the list of investigator powers
	 * @return the list of special powers
	 */
	public List<InvestigatorPower> getSpecialPowers() {
		return specialPowers;
	}

	public BufferedImage getActivePanelImage()
	{
	    return activePanelImage;
	}

	public BufferedImage getInactivePanelImage()
	{
	    return inactivePanelImage;
	}

	/**
	 * Gets the investigator's image
	 * @return the image
	 */
	public BufferedImage getImage() {
		return img;
	}

	/**
	 * Gets the displayed image, which is the upright image when the investigator
	 * is not delayed, and the rotated image when he is delayed
	 * @return the displayed image
	 */
	public BufferedImage getDisplayImage()
	{
	    if (delayed)
	    {
	        return rotatedImg;
	    }
	    else
	    {
	        return img;
	    }
	}

	/**
	 * Sets the investigator's image from a file
	 * @param filename The image file name
	 */
	public void setImage(String filename)
	{
		if (filename != null)
		{
			BufferedImage image = ImageUtils.loadImage(filename);
			image = image.getSubimage(0, 0, image.getWidth(), Math.round(image.getHeight() * 0.8f));
			image = ImageUtils.resizeImage(image, 35, 40);
			// sample middle of image
			int sr = 0, sg = 0, sb = 0;
			for (int i = 10; i < 25; i++)
			{
				for (int j = 15; j < 30; j++)
				{
					int rgb = image.getRGB(i, j);
					int red = (rgb >> 16) & 0xFF;
					int green = (rgb >> 8) & 0xFF;
					int blue = rgb & 0xFF;
					sr += red;
					sg += green;
					sb += blue;
				}
			}
			sr /= 225;
			sg /= 225;
			sb /= 225;
			textColor = ImageUtils.getContrastingColor(sr, sg, sb);

			Graphics2D g = image.createGraphics();
			// draw a "holder"
			g.setStroke(new BasicStroke(2, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER));
			g.setColor(new Color(230, 230, 230));
			g.drawLine(6, 39, 29, 39);
			g.fillRect(13, 33, 10, 7);
			g.dispose();
			img = new BufferedImage(40, 40, BufferedImage.TYPE_INT_ARGB);
			g = img.createGraphics();
			g.drawImage(image, 2, 0, null);
			g.dispose();
			rotatedImg = new BufferedImage(40, 40, BufferedImage.TYPE_INT_ARGB);
            g = rotatedImg.createGraphics();
            g.rotate(Math.toRadians(-90));
            g.drawImage(img, -40, 0, null);
            g.dispose();
		}
	}

	/**
	 * Sets the index of this investigator, ie. his position
	 * @param index the index to set
	 */
	public void setIndex(int index)
	{
		this.index = index;
	}

	/**
	 * Gets the index of this investigator, ie. his position
	 * @return the index
	 */
	public int getIndex()
	{
		return index;
	}

	/**
	 * Gets the preferred text color
	 * @return the preferred text color
	 */
	public Color getTextColor()
	{
		return textColor;
	}

	/**
	 * Gets the amount of money this investigator begins with
	 * @return the starting money
	 */
	public int getStartMoney() {
		return startMoney;
	}

	/**
	 * Gets the amount of clues this investigator begins with
	 * @return the starting clues
	 */
	public int getStartClues() {
		return startClues;
	}

	/**
	 * Gets the list of starting fixed possessions for this investigator
	 * @return the fixed possessions
	 */
	public List<Pair<String, String>> getFixedPossessions() {
		return fixedPossessions;
	}

	/**
	 * Gets the list of starting random possessions for this investigator
	 * @return the random possessions
	 */
	public List<Pair<String, Integer>> getRandomPossessions() {
		return randomPossessions;
	}

	/**
	 * Gets the username of the player playing this investigator
	 * @return the player's username
	 */
	public String getPlayer()
	{
		return player;
	}

	/**
	 * Sets the username of the player playing this investigator
	 * @param username the username to set
	 */
	public void setPlayer(String username)
	{
		this.player = username;
	}

	/**
	 * Gets the current number of clues
	 * @return the clues
	 */
	public int getClues() {
		return clues;
	}

	/**
	 * Modifies the number of clues the investigator has by an amount. If
	 * the amount is negative and the investigator has not enough clues, he
	 * loses all his clues.
	 * @param amount The amount to modify. Can be positive or negative
	 * @return true if the action was valid, false if the investigator
	 * had not enough clues to lose
	 */
	public boolean modifyClues(int amount)
	{
		if (amount + clues < 0)
		{
			clues = 0;
			return false;
		}
		clues += amount;
		return true;
	}

	/**
	 * Gets the investigator's current money
	 * @return the current money
	 */
	public int getMoney() {
		return money;
	}

	/**
	 * Modifies the number of money the investigator has by an amount. If
     * the amount is negative and the investigator has not enough money, he
     * loses all his money.
	 * @param amount The amount to modify. Can be positive or negative
	 * @return true if the action was valid, false if the investigator
	 * had not enough money
	 */
	public boolean modifyMoney(int amount)
	{
		if (amount + money < 0)
		{
			money = 0;
			return false;
		}
		money += amount;
		return true;
	}

	/**
	 * Set the skill sliders
	 * @param val the array of skill slider positions as integers
	 */
	public void setSkillSliders(int[] val)
	{
		skillSlidersSet = true;
		for (int i = 0; i < skillSlider.length; i++)
		{
			skillSlider[i] = val[i];
		}
	}
	/**
	 * Set the skill sliders
	 * @param val the array of skill slider positions as strings
	 */
	public void setSkillSliders(String[] val)
	{
		skillSlidersSet = true;
		for (int i = 0; i < skillSlider.length; i++)
		{
			skillSlider[i] = Integer.parseInt(val[i]);
		}
	}

	/**
	 * Checks if skills are initialized
	 * @return true if skills are initialized
	 */
	public boolean areSkillSlidersSet() {
		return skillSlidersSet;
	}

	/**
	 * Sets the "skill sliders set" flag during upkeep, to track
	 * if an investigator's upkeep has been performed
	 * @param flag the flag to set
	 */
	public void setSkillSliderFlag(boolean flag)
	{
	    skillSlidersSet = flag;
	}

	/**
	 * Gets the positions of the skill sliders
	 * @return the skillSliders
	 */
	public int[] getSkillSliders() {
		return skillSlider;
	}

	/**
	 * Gets the position of a particular skill slider
	 * @param index The index of that slider. Speed/Sneak = 0, Fight/Will=1
	 * Lore/Luck = 2
	 * @return The position of the skill slider. 0 is most left, 3 is most right
	 */
	public int getSkillSlider(int index)
	{
		if (index < 0 || index > 2) return -1;
		return skillSlider[index];
	}

	/**
	 * Gets the amount of remaining movement points for an investigator for
	 * this turn.
	 * @return the amount of remaining movement points
	 */
	public int getMovementPoints()
	{
		return movementPoints;
	}

	/**
	 * Sets the investigator's current movement points
	 * @param points the movement points to set
	 */
	public void setMovementPoints(int points)
	{
	    movementPoints = points;
	}

	/**
	 * Depletes an investigator's movement points
	 * @param amount the amount to deplete by. Must be positive
	 */
	public void useMovementPoints(int amount)
	{
		if (amount > 0 && amount <= movementPoints)
		{
			movementPoints -= amount;
		}
	}

	/**
	 * Checks if this investigator has been knocked unconscious or insane this round
	 * @return true if this investigator is dead
	 */
	public boolean isDead()
	{
	    return dead;
	}

	/**
     * Sets if this investigator has been knocked unconscious or insane this round
     * @param dead set to true if this investigator is dead
     */
	public void setDead(boolean dead)
	{
	    this.dead = dead;
	}

	/**
	 * Gets the investigator's current stamina
	 * @return the current stamina
	 */
	public int getCurrentStamina() {
		return curStamina;
	}

	/**
	 * Modifies the investigator's stamina
	 * @param amount the amount to modify
	 * @return the amount actually added, if positive, or the amount subtracted
	 * if negative
	 */
	public int modifyStamina(int amount)
	{
	    int orig = curStamina;
		curStamina += amount;
		if (curStamina > getMaxStaminaMod())
		{
			curStamina = getMaxStaminaMod();
		}
		else if (curStamina < 0)
		{
			curStamina = 0;
		}
		return curStamina - orig;
	}

	/**
	 * Sets the investigator's stamina
	 * @param amount the amount to set
	 */
	public void setStamina(int amount)
	{
	    curStamina = Math.min(amount, getMaxStaminaMod());
	}

	/**
	 * Gets the investigator's current sanity
	 * @return the current sanity
	 */
	public int getCurrentSanity() {
		return curSanity;
	}

	/**
	 * Modifies the investigator's sanity
	 * @param amount the amount to modify
	 * @return the amount actually added, if positive, or the amount subtracted
     * if negative
	 */
	public int modifySanity(int amount)
	{
	    int orig = curSanity;
		curSanity += amount;
		if (curSanity > getMaxSanityMod())
		{
			curSanity = getMaxSanityMod();
		}
		else if (curSanity < 0)
		{
			curSanity = 0;
		}
		return curSanity - orig;
	}

	/**
     * Sets the investigator's sanity
     * @param amount the amount to set
     */
    public void setSanity(int amount)
    {
        curSanity = Math.min(amount, getMaxSanityMod());
    }

	/**
	 * Gets the name of investigator's current location
	 * @return the current location's name
	 */
	public String getLocationName() {
		return location;
	}

	/**
	 * Gets the location of the investigator
	 * @return the location
	 */
	public LocationContainer getLocation()
	{
		return locContainer;
	}

	/**
	 * Sets the investigator's current location
	 * @param location the current location
	 */
	public void setLocation(LocationContainer cont)
	{
		this.location = cont.getName();
		this.locContainer = cont;
	}

	/**
	 * Gets the list of effects on this investigator. This returns a view-only
	 * list. Use applyStatus and removeStatus to add or remove status effects.
	 * @return the status effects
	 */
	public List<PersonalEffect> getStatusEffects() {
		return Collections.unmodifiableList(statusEffects);
	}

	/**
	 * Applies a specific status effect on this investigator
	 * @param peff the effect to apply
	 */
	public void applyStatus(PersonalEffect peff)
	{
		statusEffects.add(peff);
		calculateSkillMods();
	}

	/**
	 * Removes a specific status effect from this investigator
	 * @param peff the effect to apply
	 */
	public void removeStatus(PersonalEffect peff)
	{
		statusEffects.remove(peff);
		calculateSkillMods();
	}

	/**
	 * Removes a specific status effect from this investigator
	 * @param name the name of the status effect to remove
	 */
	public PersonalEffect removeStatus(String name)
	{
		PersonalEffect peff = GameData.removeByName(statusEffects, name);
		calculateSkillMods();
		return peff;
	}

	/**
	 * Checks if the investigator is delayed
	 * @return true if the investigator is delayed
	 */
	public boolean isDelayed() {
		return delayed;
	}

	/**
	 * Sets if the investigator is delayed
	 * @param delayed set to true to delay the investigator, false to refresh the investigator
	 */
	public void setDelayed(boolean delayed)
	{
		this.delayed = delayed;
	}

    /**
	 * Gets whether the investigator's movement effects are disabled
	 * due to having recovered from a delay, or completed a combat
     * @return the movementEffectsLocked true if movement effects are
     * disabled
     */
    public boolean areMovementEffectsLocked()
    {
        return movementEffectsLocked;
    }

    /**
     * Sets whether the investigator's movement effects are disabled
     * due to having recovered from a delay, or completed a combat
     * @param movementEffectsLocked true if movement
     * effects are to be disabled
     */
    public void setMovementEffectsLocked(boolean movementEffectsLocked)
    {
        this.movementEffectsLocked = movementEffectsLocked;
    }

    /**
     * Gets whether the investigator's movement points are locked. This can
     * occur when the investigator is delayed, or in another world, or
     * has completed a combat.
     * @return true if the movement points are locked
     */
    public boolean areMovementPointsLocked()
    {
        return movementPointsLocked;
    }

    /**
     * Sets whether the investigator's movement points are locked. This can
     * occur when the investigator is delayed, or in another world, or
     * has completed a combat.
     * @param movementPointsLocked true if movement points are locked
     */
    public void setMovementPointsLocked(boolean movementPointsLocked)
    {
        this.movementPointsLocked = movementPointsLocked;
    }

    /**
     * Checks if the investigator has an explored marker
     * @return the exploredMarker true if the investigator has an explored
     * marker
     */
    public boolean hasExploredMarker()
    {
        return exploredMarker;
    }

    /**
     * Sets whether the investigator has an explored marker
     * @param exploredMarker set to true if the investigator has an explored
     * marker
     */
    public void setExploredMarker(boolean exploredMarker)
    {
        this.exploredMarker = exploredMarker;
    }

    /**
     * Sets if this investigator has successfully avoided monsters
     * @param avoidedMonsters set to true if monsters have been avoided
     */
    public void setAvoidedMonsters(boolean avoidedMonsters)
    {
        this.avoidedMonsters = avoidedMonsters;
    }

    /**
     * Checks if this investigator has successfully avoided monsters
     * @return true if this investigator has successfully avoided monsters
     * in his current location
     */
    public boolean hasAvoidedMonsters()
    {
        return avoidedMonsters;
    }

    /**
	 * Gets the list of investigator cards possessed
	 * @return the investigator cards
	 */
	public List<InvestigatorCard> getInvcards() {
		return invcards;
	}

	/**
	 * Sort the investigator cards by type and name
	 */
	public void sortInvcards()
	{
		Collections.sort(invcards);
	}

	/**
	 * Gets the list of items (common, unique, spells) possessed
	 * by this investigator
	 * @return the list of items
	 */
	public List<InvestigatorCard> getItems()
	{
	    ArrayList<InvestigatorCard> items = new ArrayList<InvestigatorCard>();
	    for (InvestigatorCard card : invcards)
	    {
	        if (card.isItem())
	        {
	            items.add(card);
	        }
	    }
	    return items;
	}

	/**
	 * Gets the list of investigator cards of a particular type
	 * @param type the type of investigator card
	 * @return the list of cards matching that type
	 */
	public List<InvestigatorCard> getCardsOfType(InvestigatorCardType type)
	{
	    ArrayList<InvestigatorCard> items = new ArrayList<InvestigatorCard>();
        for (InvestigatorCard card : invcards)
        {
            if (card.getType().equals(type))
            {
                items.add(card);
            }
        }
        return items;
	}

	/**
	 * Gets a list of all weapons belonging to this investigator that are
	 * "always-on"
	 * @return a list of all permanent weapons
	 */
	public List<EffectSource> getPermanentWeapons()
	{
	    List<EffectSource> weaponList = new ArrayList<EffectSource>();
	    for (EffectSource effSrc : getInvcards())
	    {
	        for (ModEffect mod : effSrc.getModEffects())
	        {
	            if (mod.getUsage().equals(EffectUsage.ALWAYS) && mod.getType().equals("Weapon"))
	            {
	                weaponList.add(effSrc);
	                break;
	            }
	        }
	    }
	    for (EffectSource effSrc : getStatusEffects())
        {
            for (ModEffect mod : effSrc.getModEffects())
            {
                if (mod.getUsage().equals(EffectUsage.ALWAYS) && mod.getType().equals("Weapon"))
                {
                    weaponList.add(effSrc);
                    break;
                }
            }
        }
	    return weaponList;
	}

	/**
	 * Gets a list of all physical weapons belonging to the investigator
	 * @return the list of physical weapons
	 */
	public List<InvestigatorCard> getPhysicalWeapons()
	{
	    List<InvestigatorCard> list = new ArrayList<InvestigatorCard>();
	    for (InvestigatorCard card : invcards)
	    {
	        if (card instanceof CommonItem)
	        {
	            if (((CommonItem)card).getSubtype().equals(CommonItemSubtype.PHYS_WEAPON))
	            {
	                list.add(card);
	            }
	        }
	        else if (card instanceof UniqueItem)
            {
                if (((UniqueItem)card).getSubtype().equals(UniqueItemSubtype.PHYS_WEAPON))
                {
                    list.add(card);
                }
            }
	    }
	    return list;
	}

	/**
	 * Refreshes all this investigator's cards and powers
	 */
	public void refreshCards()
	{
	    for (InvestigatorCard invcard : invcards)
	    {
	        invcard.setExhausted(false);
	    }
	    for (PersonalEffect peff : statusEffects)
	    {
	        peff.setExhausted(false);
	    }
	    for (InvestigatorPower power : specialPowers)
	    {
	        power.setExhausted(false);
	    }
	}

	/**
	 * The investigator gains an investigator card. Register any perm effects
	 * it has.
	 * @param card the card to gain
	 */
	public void gainInvestigatorCard(InvestigatorCard card)
	{
		invcards.add(card);
		calculateSkillMods();
	}

	/**
	 * The investigator discards an investigator card. Remove any perm effects it has
	 * @param card the card to discard
	 */
	public void loseInvestigatorCard(InvestigatorCard card)
	{
		invcards.remove(card);
		calculateSkillMods();
	}

	/**
	 * The investigator discards an investigator card based on its id. Remove any
	 * perm effects it has
	 * @param id the id of the card to discard
	 * @return the card discarded
	 */
	public InvestigatorCard loseInvestigatorCard(int id)
	{
	    InvestigatorCard card = GameData.removeById(invcards, id);
	    calculateSkillMods();
	    return card;
	}

	/**
	 * Gets the list of monster trophies possessed
	 * @return the list of monster trophies
	 */
	public List<Monster> getMonsterTrophies()
	{
		return monsterTrophies;
	}

	/**
	 * Gets the list of gate trophies possessed
	 * @return the list of gate trophies
	 */
	public List<GateTile> getGateTrophies()
	{
		return gateTrophies;
	}

	/**
	 * Searches all investigator items and powers for modifiers of
	 * a certain type
	 * @param type the type to search for
	 * @return a list of all modifiers of that type
	 */
	public List<ModEffect> searchModifiers(String type)
	{
		List<ModEffect> list = new ArrayList<ModEffect>();
		for (InvestigatorPower power : specialPowers)
		{
			power.searchModEffects(list, type);
		}
		for (PersonalEffect peff : statusEffects)
		{
			peff.searchModEffects(list, type);
		}
		for (InvestigatorCard card : invcards)
		{
			card.searchModEffects(list, type);
		}
		return list;
	}

	/**
     * Searches all investigator items and powers for modifiers of
     * a certain type
     * @param type the type to search for
     * @param list a list to add to
     */
    public void searchModifiers(String type, List<ModEffect> list)
    {
        for (InvestigatorPower power : specialPowers)
        {
            power.searchModEffects(list, type);
        }
        for (PersonalEffect peff : statusEffects)
        {
            peff.searchModEffects(list, type);
        }
        for (InvestigatorCard card : invcards)
        {
            card.searchModEffects(list, type);
        }
    }

	/**
	 * Searches all investigator items and powers for modifiers of
	 * a certain type
	 * @param type the type to search for
	 * @param details the details to search for
	 * @return a list of all modifiers of that type
	 */
	public List<ModEffect> searchModifiers(String type, String details)
	{
		List<ModEffect> list = new ArrayList<ModEffect>();
		for (InvestigatorPower power : specialPowers)
		{
			power.searchModEffects(list, type, details);
		}
		for (PersonalEffect peff : statusEffects)
		{
			peff.searchModEffects(list, type, details);
		}
		for (InvestigatorCard card : invcards)
		{
			card.searchModEffects(list, type, details);
		}
		return list;
	}

	/**
     * Searches all investigator items and powers for modifiers of
     * a certain type
     * @param type the type to search for
     * @param details the details to search for
     * @param list a list to add to
     */
    public void searchModifiers(String type, String details, List<ModEffect> list)
    {
        for (InvestigatorPower power : specialPowers)
        {
            power.searchModEffects(list, type, details);
        }
        for (PersonalEffect peff : statusEffects)
        {
            peff.searchModEffects(list, type, details);
        }
        for (InvestigatorCard card : invcards)
        {
            card.searchModEffects(list, type, details);
        }
    }

    /**
     * Searches this investigator for the mod-effect with the particular source string,
     * type and details
     * @param sourceString the source string
     * @param type the type
     * @param details the details
     * @return the mod effect found, or null if not found
     */
    public ModEffect searchModifier(String sourceString, String type, String details)
    {
        List<ModEffect> list = new ArrayList<ModEffect>();
        if (sourceString.startsWith("#"))
        {
            int id = Integer.parseInt(sourceString.substring(1));
            InvestigatorCard card = GameData.retrieveById(invcards, id);
            card.searchModEffects(list, type, details);
        }
        else if (sourceString.startsWith("%"))
        {
            String name = sourceString.substring(1);
            PersonalEffect peff = GameData.retrieveByName(statusEffects, name);
            peff.searchModEffects(list, type, details);
        }
        else
        {
            InvestigatorPower ipower = GameData.retrieveByName(specialPowers, sourceString);
            ipower.searchModEffects(list, type, details);
        }
        if (list.isEmpty())
        {
            return null;
        }
        else
        {
            return list.get(0);
        }
    }

	/**
	 * Checks if the investigator has any item or power which gives him a
	 * certain modifier.
	 * @param type the type to search for
	 * @param details the details to search for
	 * @return true if the investigator has that modifier, false otherwise
	 */
	public boolean hasModifier(String type, String details)
	{
		List<ModEffect> list = new ArrayList<ModEffect>();
		for (InvestigatorPower power : specialPowers)
		{
			power.searchModEffects(list, type, details);
			if (list.size() > 0)
			{
				return true;
			}
		}
		for (PersonalEffect peff : statusEffects)
		{
			peff.searchModEffects(list, type, details);
			if (list.size() > 0)
			{
				return true;
			}
		}
		for (InvestigatorCard card : invcards)
		{
			card.searchModEffects(list, type, details);
			if (list.size() > 0)
			{
				return true;
			}
		}
		return false;
	}

	/**
	 * Searches an investigator's cards, powers and status effects for
	 * usable effects at a certain phase
	 * @param phase the phase to search
	 * @return the list of usable effects
	 */
	public List<UsableEffect> searchUsableEffects(UsablePhase phase)
	{
	    List<UsableEffect> list = new ArrayList<UsableEffect>();
	    for (InvestigatorCard card : invcards)
	    {
	        card.searchUsableEffects(list, phase);
	    }
	    for (PersonalEffect peff : statusEffects)
	    {
	        peff.searchUsableEffects(list, phase);
	    }
	    for (InvestigatorPower invpower : specialPowers)
	    {
	        invpower.searchUsableEffects(list, phase);
	    }
	    return list;
	}

	/**
     * Searches an investigator's cards, powers and status effects for
     * usable effects at a certain phase, and adds to a list
     * @param list the list to add to
     * @param phase the phase to search
     */
    public void searchUsableEffects(List<UsableEffect> list, UsablePhase phase)
    {
        for (InvestigatorCard card : invcards)
        {
            card.searchUsableEffects(list, phase);
        }
        for (PersonalEffect peff : statusEffects)
        {
            peff.searchUsableEffects(list, phase);
        }
        for (InvestigatorPower invpower : specialPowers)
        {
            invpower.searchUsableEffects(list, phase);
        }
    }

	/**
     * Searches an investigator's cards, powers and status effects for
     * usable effects at a certain phase only and that trigger
     * automatically
     * @param phase the phase to search
     * @return the list of usable effects
     */
    public List<UsableEffect> searchAutoUsableEffects(UsablePhase phase)
    {
        List<UsableEffect> list = new ArrayList<UsableEffect>();
        for (InvestigatorCard card : invcards)
        {
            card.searchAutoUsableEffects(list, phase);
        }
        for (PersonalEffect peff : statusEffects)
        {
            peff.searchAutoUsableEffects(list, phase);
        }
        for (InvestigatorPower invpower : specialPowers)
        {
            invpower.searchAutoUsableEffects(list, phase);
        }
        return list;
    }

    /**
     * Searches this investigator for the usable-effect with the particular source string,
     * and id
     * @param sourceString the source string
     * @param uid the usable effect id
     * @return the usable effect found, or null if not found
     */
    public UsableEffect searchUsableEffect(String sourceString, int uid)
    {
        List<UsableEffect> list = new ArrayList<UsableEffect>();
        if (sourceString.startsWith("#"))
        {
            int id = Integer.parseInt(sourceString.substring(1));
            InvestigatorCard card = GameData.retrieveById(invcards, id);
            for (UsableEffect ueff : card.getUsableEffects())
            {
                if (ueff.getId() == uid)
                {
                    list.add(ueff);
                }
            }
        }
        else if (sourceString.startsWith("%"))
        {
            String name = sourceString.substring(1);
            PersonalEffect peff = GameData.retrieveByName(statusEffects, name);
            for (UsableEffect ueff : peff.getUsableEffects())
            {
                if (ueff.getId() == uid)
                {
                    list.add(ueff);
                }
            }
        }
        else
        {
            InvestigatorPower ipower = GameData.retrieveByName(specialPowers, sourceString);
            for (UsableEffect ueff : ipower.getUsableEffects())
            {
                if (ueff.getId() == uid)
                {
                    list.add(ueff);
                }
            }
        }
        if (list.isEmpty())
        {
            return null;
        }
        else
        {
            return list.get(0);
        }
    }

    /**
     * Searches this investigator for the effect source with the specified source
     * string
     * @param sourceString the source string
     * @return the effect source found, or null if not found
     */
    public EffectSource searchEffectSource(String sourceString)
    {
        if (sourceString.startsWith("#"))
        {
            int id = Integer.parseInt(sourceString.substring(1));
            InvestigatorCard card = GameData.retrieveById(invcards, id);
            return card;
        }
        else if (sourceString.startsWith("%"))
        {
            String name = sourceString.substring(1);
            PersonalEffect peff = GameData.retrieveByName(statusEffects, name);
            return peff;
        }
        else
        {
            InvestigatorPower ipower = GameData.retrieveByName(specialPowers, sourceString);
            return ipower;
        }
    }



	/**
	 * Sets the currently used effect
     * @param currentUsedEffect the current used effect
     */
    public void setCurrentUsedEffect(UsableEffect currentUsedEffect)
    {
        this.currentUsedEffect = currentUsedEffect;
    }

    /**
     * Gets the currently used effect
     * @return the currently used effect
     */
    public UsableEffect getCurrentUsedEffect()
    {
        return currentUsedEffect;
    }

    @Override
	public String toString()
	{
		return name + ", the " + profession;
	}

	@Override
	public boolean equals(Object o)
	{
		if (o instanceof Investigator)
		{
			Investigator inv = (Investigator)o;
			return getName().equals(inv.getName());
		}
		return false;
	}

	/**
	 * This class represents an investigator power. All investigators possess
	 * between 1 and 3 powers.
	 * @author ctheng
	 *
	 */
	public static class InvestigatorPower implements EffectSource
	{
		private String name;
		private String description;
		private boolean exhausted;
		private int numCounters, limit;
		private List<ModEffect> modEffects;
		private List<UsableEffect> usableEffects;

		public InvestigatorPower(String name)
		{
			this.name = name;
			numCounters = 0;
			limit = 0;
			modEffects = new ArrayList<ModEffect>();
			usableEffects = new ArrayList<UsableEffect>();
		}

		/**
		 * Gets the description of the power
		 * @return the description
		 */
		public String getDescription() {
			return description;
		}

		/**
		 * Sets the description of the power
		 * @param description the description to set
		 */
		public void setDescription(String description) {
			this.description = description;
		}

		/**
		 * Gets the name of the power
		 * @return the name
		 */
		public String getName() {
			return name;
		}

		/*
		 * (non-Javadoc)
		 * @see arkham.data.ModifierSource#setExhausted(boolean)
		 */
		public void setExhausted(boolean exhausted) {
			this.exhausted = exhausted;
		}

		/*
		 * (non-Javadoc)
		 * @see arkham.data.ModifierSource#isExhausted()
		 */
		public boolean isExhausted() {
			return exhausted;
		}

		/*
		 * (non-Javadoc)
		 * @see arkham.data.ModifierSource#getModEffects()
		 */
		public List<ModEffect> getModEffects()
		{
			return modEffects;
		}

		/*
		 * (non-Javadoc)
		 * @see arkham.data.ModifierSource#searchModEffects(java.lang.String)
		 */
		public List<ModEffect> searchModEffects(String type)
		{
			List<ModEffect> list = new ArrayList<ModEffect>();
			for (ModEffect mod : modEffects)
			{
				if (mod.getType().equalsIgnoreCase(type))
				{
					list.add(mod);
				}
			}
			return list;
		}

		/*
		 * (non-Javadoc)
		 * @see arkham.data.ModifierSource#searchModEffects(java.util.List, java.lang.String)
		 */
		public void searchModEffects(List<ModEffect> list, String type)
		{
			for (ModEffect mod : modEffects)
			{
				if (mod.getType().equalsIgnoreCase(type))
				{
					list.add(mod);
				}
			}
		}

		/*
		 * (non-Javadoc)
		 * @see arkham.data.ModifierSource#searchModEffects(java.lang.String)
		 */
		public List<ModEffect> searchModEffects(String type, String details)
		{
			List<ModEffect> list = new ArrayList<ModEffect>();
			for (ModEffect mod : modEffects)
			{
				if (mod.getType().equalsIgnoreCase(type) &&
						mod.getDetails().equalsIgnoreCase(details))
				{
					list.add(mod);
				}
			}
			return list;
		}

		/*
		 * (non-Javadoc)
		 * @see arkham.data.ModifierSource#searchModEffects(java.util.List, java.lang.String)
		 */
		public void searchModEffects(List<ModEffect> list, String type, String details)
		{
			for (ModEffect mod : modEffects)
			{
				if (mod.getType().equalsIgnoreCase(type) &&
						mod.getDetails().equalsIgnoreCase(details))
				{
					list.add(mod);
				}
			}
		}

		/*
		 * (non-Javadoc)
		 * @see arkham.data.ModifierSource#setNumCounters(int)
		 */
		public void setNumCounters(int numCounters) {
			this.numCounters = numCounters;
		}

		/*
		 * (non-Javadoc)
		 * @see arkham.data.ModifierSource#getNumCounters()
		 */
		public int getNumCounters() {
			return numCounters;
		}

        @Override
        public int getLimit()
        {
            return limit;
        }

        @Override
        public void setLimit(int limit)
        {
            this.limit = limit;
        }

		/*
		 * (non-Javadoc)
		 * @see arkham.data.EffectSource#getUsableEffects()
		 */
		public List<UsableEffect> getUsableEffects() {
			return usableEffects;
		}

		/*
         * (non-Javadoc)
         * @see arkham.data.EffectSource#searchUsableEffects(arkham.data.Globals.UsablePhase)
         */
        public List<UsableEffect> searchUsableEffects(UsablePhase phase)
        {
            List<UsableEffect> list = new ArrayList<UsableEffect>();
            for (UsableEffect ueff : usableEffects)
            {
                if (ueff.getPhase().equals(phase) && !ueff.getUsage().equals(EffectUsage.ALWAYS))
                {
                    list.add(ueff);
                }
            }
            return list;
        }

        @Override
        public void searchUsableEffects(List<UsableEffect> list,
                UsablePhase phase)
        {
            for (UsableEffect ueff : usableEffects)
            {
                if (ueff.getPhase().equals(phase) && !ueff.getUsage().equals(EffectUsage.ALWAYS))
                {
                    list.add(ueff);
                }
            }
        }

        @Override
        public List<UsableEffect> searchAutoUsableEffects(UsablePhase phase)
        {
            List<UsableEffect> list = new ArrayList<UsableEffect>();
            for (UsableEffect ueff : usableEffects)
            {
                if (ueff.getPhase().equals(phase) && ueff.getUsage().equals(EffectUsage.ALWAYS))
                {
                    list.add(ueff);
                }
            }
            return list;
        }

        @Override
        public void searchAutoUsableEffects(List<UsableEffect> list,
                UsablePhase phase)
        {
            for (UsableEffect ueff : usableEffects)
            {
                if (ueff.getPhase().equals(phase) && ueff.getUsage().equals(EffectUsage.ALWAYS))
                {
                    list.add(ueff);
                }
            }
        }

        @Override
        public List<ModEffect> searchAlwaysModEffects(String type)
        {
            List<ModEffect> list = new ArrayList<ModEffect>();
            for (ModEffect mod : modEffects)
            {
                if (mod.getType().equalsIgnoreCase(type) &&
                        mod.getUsage().equals(EffectUsage.ALWAYS))
                {
                    list.add(mod);
                }
            }
            return list;
        }

        @Override
        public List<ModEffect> searchAlwaysModEffects(String type, String details)
        {
            List<ModEffect> list = new ArrayList<ModEffect>();
            for (ModEffect mod : modEffects)
            {
                if (mod.getType().equalsIgnoreCase(type) &&
                        mod.getDetails().equalsIgnoreCase(details) &&
                        mod.getUsage().equals(EffectUsage.ALWAYS))
                {
                    list.add(mod);
                }
            }
            return list;
        }

        @Override
        public String getIdentifier()
        {
            return name;
        }

		@Override
		public String toString() {
			return name + ": " + description;
		}

		@Override
		public boolean equals(Object o)
		{
			if (o instanceof InvestigatorPower)
			{
				InvestigatorPower invpower = (InvestigatorPower)o;
				return name.equals(invpower.getName());
			}
			return false;
		}
	}

	/**
	 * Creates the data representation of the investigator from the XML info
	 * and performs a stringent validation check to make sure the XML data is
	 * valid.
	 * <br>Will return <b>null</b> on a failure.
	 *
	 * @param xmlinv the XML investigator info
	 * @return the data representation of the investigator
	 */
	public static Investigator createInvestigatorFromXml(InvestigatorType xmlinv)
	{
		Investigator inv = new Investigator(xmlinv.getName());
		/* Load data */
		inv.profession = xmlinv.getProfession();
		inv.maxStamina = xmlinv.getMaxStamina();
		inv.maxSanity = xmlinv.getMaxSanity();
		inv.baseFocus = xmlinv.getFocus();
		inv.baseSkill[InvestigatorSkill.SPEED.index()] = xmlinv.getBaseSpeed();
		inv.baseSkill[InvestigatorSkill.SNEAK.index()] = xmlinv.getBaseSneak();
		inv.baseSkill[InvestigatorSkill.FIGHT.index()] = xmlinv.getBaseFight();
		inv.baseSkill[InvestigatorSkill.WILL.index()] = xmlinv.getBaseWill();
		inv.baseSkill[InvestigatorSkill.LORE.index()] = xmlinv.getBaseLore();
		inv.baseSkill[InvestigatorSkill.LUCK.index()] = xmlinv.getBaseLuck();
		inv.startLocation = xmlinv.getHome();
		inv.backgroundStory = xmlinv.getBackground();
		inv.setImage(xmlinv.getImg());

		inv.startClues = xmlinv.getStartClues();
		inv.startMoney = xmlinv.getStartMoney();
		for (FixedPossessionType fptype : xmlinv.getFixedPossession())
		{
			Pair<String, String> pair =
				new Pair<String, String>(fptype.getName(), fptype.getType());
			inv.fixedPossessions.add(pair);
		}

		for (RandomPossessionType rptype : xmlinv.getRandomPossession())
		{
			Pair<String, Integer> pair =
				new Pair<String, Integer>(rptype.getType(), rptype.getQty());
			if (rptype.getQty() < 1)
			{
				AHLogger.error(inv.name + ": Cannot start with " +
						rptype.getQty() + " " + rptype.getType() + "s.");
				return null;
			}
			inv.randomPossessions.add(pair);
		}

		for (SpecialPowerType sptype : xmlinv.getSpecialPower())
		{
			InvestigatorPower invpower = new InvestigatorPower(sptype.getName());
			invpower.setDescription(sptype.getDescription());
			if (sptype.getLimit() != null)
			{
			    invpower.setLimit(sptype.getLimit());
			}
			for (ModEffectType xmleffect : sptype.getModEffect())
			{
				ModEffect mod = ModEffect.createModEffectFromXml(xmleffect);
				if (mod == null)
				{
					return null;
				}
				mod.setSource(invpower);
				invpower.getModEffects().add(mod);
			}
			for (UsableEffectType xmleffect : sptype.getUsableEffect())
	        {
	            UsableEffect ueff = UsableEffect.createModEffectFromXml(xmleffect);
	            if (ueff == null)
	            {
	                return null;
	            }
	            ueff.setSource(invpower);
	            invpower.getUsableEffects().add(ueff);
	        }
			inv.specialPowers.add(invpower);

		}
		/* Validate fields */
		if (inv.maxSanity < 1 || inv.maxStamina < 1)
		{
			AHLogger.error(inv.name + ": Maximum stamina and sanity must be at least 1.");
			return null;
		}

		if (inv.baseFocus < 1 || inv.baseFocus > 4)
		{
			AHLogger.error(inv.name + ": Base focus must be between 1-3 or 4(infinity)");
			return null;
		}
		for (int i = 0; i < inv.baseSkill.length; i++)
		{
			if (inv.baseSkill[i] < 0 || inv.baseSkill[i] > 9)
			{
				AHLogger.error(inv.name + ": Invalid base " +
						InvestigatorSkill.getSkillName(i) + ". Must be between 1-9.");
				return null;
			}
		}
		if (inv.startLocation.equals(""))
		{
			AHLogger.error(inv.name + ": No start location specified.");
			return null;
		}
		if (inv.getStartClues() < 0)
		{
			AHLogger.error(inv.name + ": Cannot have negative starting clues.");
			return null;
		}
		if (inv.getStartMoney() < 0)
		{
			AHLogger.error(inv.name + ": Cannot have negative starting money.");
			return null;
		}

		return inv;
	}

}
